﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using CoolEngine.Engine.Input;


namespace CoolEngine.Engine
{
    [Serializable]
    public class CommandNotFoundException : Exception
    {
        public string Command { get; private set; }
        public ReadOnlyCollection<string> Arguments { get; private set; }
        public string CommandLine { get; private set; }
        public CommandNotFoundException() { }
        public CommandNotFoundException(string message, string command, string[] arguments, string commandLine)
            : base(message)
        {
            Command = command;
            Arguments = new ReadOnlyCollection<string>(arguments);
            CommandLine = commandLine;
        }
        public CommandNotFoundException(string message, Exception inner, string command, string[] arguments, string commandLine)
            : base(message, inner)
        {
            Command = command;
            Arguments = new ReadOnlyCollection<string>(arguments);
            CommandLine = commandLine;
        }
        protected CommandNotFoundException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }
    public class Command
    {
        public Command(string name,string module,int parameters, string description, string help, Func<string[],string> target)
        {
            Name = name;
            Module = module;
            Parameters = parameters;
            Description = description;
            Help = help;
            Target = target;
        }
        public string Name { get; private set; }
        public string Module { get; set; }
        public string Description { get; private set; }
        public string Help { get; private set; }
        public int Parameters { get; set; }
        public Func<string[], string> Target { get; private set; }
    }
    /// <summary>
    /// Binds a method to a command.
    /// </summary>
    [global::System.AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public sealed class CommandAttribute : Attribute
    {
        public CommandAttribute()
        {
        }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        public string Name { get; set; }
        /// <summary>
        /// Gets or sets the description.
        /// </summary>
        /// <value>The description.</value>
        public string Description { get; set; }
        /// <summary>
        /// Gets or sets the help.
        /// </summary>
        /// <value>The help.</value>
        public string Help { get; set; }
        /// <summary>
        /// Gets or sets the command.
        /// </summary>
        /// <value>The command.</value>
        public string Command { get; set; }
        public string Module { get; set; }
        public int Parameters { get; set; }
    }
    [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
    public sealed class ContainsBoundCommands : Attribute
    {

    }
    /// <summary>
    /// A service to execute command strings.
    /// </summary>
    public static class HardCodedCommandParser
    {
        private static Dictionary<string, Command> boundCommands = new Dictionary<string, Command>();
        public static Dictionary<string,Command> Commands {get{return boundCommands;}}
        /// <summary>
        /// Registers a command so it can be executed by a command string.
        /// </summary>
        /// <param name="commandName">Name of the command.</param>
        /// <param name="command">The command.</param>
        public static void RegisterCommand(string commandName, Command command)
        {
            boundCommands.Add(commandName.ToLower(), command);
        }

        /// <summary>
        /// Executes a command.
        /// </summary>
        /// <param name="commandLine">The command line.</param>
        /// <exception cref="CoolEngine.Engine.CommandNotFoundException">Thrown when the command isn't a valid command.</exception>
        public static string RunCommand(string commandLine)
        {
            string[] tokenised = commandLine.Tokenise();
            string command = tokenised[0].ToLower();
            string[] arguments = tokenised.Skip(1).ToArray();
            if (boundCommands.ContainsKey(command))
            {
                Command c = boundCommands[command];
                return c.Target(arguments);
            }
            else
            {
                throw new CommandNotFoundException("Command not found", command, arguments, commandLine);
            }
        }
        /// <summary>
        /// Scans the process for bound commands and registers them.
        /// </summary>
        public static void ScanForBoundCommands()
        {
            ScanForBoundCommands(AppDomain.CurrentDomain.GetAssemblies());
        }
        /// <summary>
        /// Scans for bound commands and registers them.
        /// </summary>
        /// <param name="assemblies">The assemblies to search.</param>
        [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect")]
        public static void ScanForBoundCommands(Assembly[] assemblies)
        {
            foreach (Assembly assembly in assemblies.Where(assembly=> assembly.GetCustomAttributes(typeof(ContainsBoundCommands), false).Length > 0))
            {
                foreach (Type type in assembly.GetTypes().Where(type=>type.GetCustomAttributes(typeof(ContainsBoundCommands),true).Length > 0))
                {
                    foreach (MethodInfo method in type.GetMethods(BindingFlags.Static| BindingFlags.Public|BindingFlags.NonPublic))
                    {
                        foreach (object attribute in method.GetCustomAttributes(typeof(CommandAttribute), true))
                        {
                            CommandAttribute c = (CommandAttribute)attribute;

                            Func<string[], string> target = (Func<string[],string>)Delegate.CreateDelegate(typeof(Func<string[],string>), method);
                            Command command = new Command(c.Name,c.Module,c.Parameters, c.Description, c.Help, target);
                            RegisterCommand(c.Command, command);
                            foreach (object bindKeyAttribute in method.GetCustomAttributes(typeof(DefaultBoundKeyAttribute), true))
                            {
                                DefaultBoundKeyAttribute bind = (DefaultBoundKeyAttribute)bindKeyAttribute;
                                InputHandler.BindKey(bind.Key, c.Command, bind.ButtonPressStage);
                            }
                        }
                    }
                }
            }
            GC.Collect(0, GCCollectionMode.Optimized);
        }
    }
}
